﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.IO;
using System.Xml;
using System.Data;
using System.Data.SqlServerCe;
using System.Windows.Forms;

namespace ProjetoFase2
{
    class ServerInteraction
    {
        private TcpClient clientSocket;
        private string serverIp;
        private int port;
        NetworkStream stream;
        UserLogin userInfo;
        private DatabaseConnector dbCon;

        public ServerInteraction(string serverIp, int port)
        {
            this.serverIp = serverIp;
            this.port = port;
            this.userInfo = new UserLogin();
        }

        public ServerInteraction(string serverIp, int port, UserLogin userInfo)
        {
            this.serverIp = serverIp;
            this.port = port;
            this.userInfo = userInfo;
        }

        public string readString(Stream reader)
        {
            int bytesRead, l;
            byte[] packetSize = new byte[sizeof(Int32)];
            byte[] data;
            bytesRead = reader.Read(packetSize, 0, packetSize.Length);
            l = BitConverter.ToInt32(packetSize, 0);
            data = new byte[l];
            bytesRead = reader.Read(data, 0, data.Length);

            if (bytesRead == 0)
            {
                return null;
            }
            else
            {
                return Encoding.UTF8.GetString(data, 0, data.Length);
            }
        }

        public bool writeString(Stream writer, string text)
        {
            if (text == null)
                text = "";

            byte[] data = Encoding.UTF8.GetBytes(text);

            if (data.Length == 0)
            {
                return false;
            }
            else
            {
                writer.Write(BitConverter.GetBytes(data.Length), 0, sizeof(Int32));
                writer.Write(data, 0, data.Length);
                return true;
            }
        }

        public DataSet readDataSet(Stream reader)
        {
            DataSet dataSet = new DataSet();
            string xmlDataSet = readString(reader);

            if (xmlDataSet != null)
            {
                dataSet.ReadXml(new StringReader(xmlDataSet));
                return dataSet;
            }
            else
            {
                return null;
            }
        }

        public bool writeDataSet(Stream writer, DataSet dataSet)
        {
            StringWriter strWriter = new StringWriter();
            dataSet.WriteXml(strWriter, XmlWriteMode.WriteSchema);
            string xmlDataSet = strWriter.ToString();

            return writeString(writer, xmlDataSet);
        }

        public void instantiateDatabase(Technician auxTech)
        {
            DatabaseConnector dbCon = new DatabaseConnector(userInfo.Username, userInfo.Password);
            dbCon.createDatabase();
            dbCon.createTableTechnician();
            dbCon.createTableResidence("active");
            dbCon.createTableResidence("inactive");
            dbCon.createTableVersionVector("write");
            dbCon.insertTechnician(auxTech);
        }

        public bool restoreDatabase(string username, string password, UserLogin userLogin)
        {
            try
            {
                // get version vector dataset
                DatabaseConnector dbCon = new DatabaseConnector(username, password);

                if (!dbCon.existsDatabase())
                    return false;

                Technician auxTech = dbCon.selectTechnician(username);

                userLogin.Username = auxTech.Id;
                userLogin.Name = auxTech.Name;
                userLogin.District = auxTech.District;
                userLogin.Password = auxTech.Password;
                userLogin.AccessLevel = auxTech.Category;
                userLogin.Supervisor = auxTech.Supervisor;

                return true;
            }
            catch (Exception ex)
            {
                MessageBox.Show("Login data invalid or no database found!");
                return false;
            }
        }

        public UserLogin executeLogin(string username, string password)
        {
            bool loginValid = false;

            try
            {
                clientSocket = new TcpClient(this.serverIp, this.port);

                if (clientSocket.Client.Connected)
                {
                    stream = clientSocket.GetStream();
                    // write data
                    writeString(stream, "read-login;" + username + ";" + password);

                    // check to see if data is valid
                    string dataValid = readString(stream);

                    if (dataValid == "valid")
                    {
                        // read data from server
                        DataSet ds = readDataSet(stream);
                        // obtain datareader
                        DataTableReader dtReader = ds.CreateDataReader();

                        // read data
                        if (dtReader.Read())
                        {
                            Technician auxTech = new Technician(dtReader);
                            userInfo.Username = auxTech.Id;
                            userInfo.Name = auxTech.Name;
                            userInfo.District = auxTech.District;
                            userInfo.Password = auxTech.Password;
                            userInfo.AccessLevel = auxTech.Category;

                            if (auxTech.Supervisor != null)
                                userInfo.Supervisor = auxTech.Supervisor;

                            // create local database and populate
                            instantiateDatabase(auxTech);

                            loginValid = true;
                        }
                    }
                    else
                    {
                        MessageBox.Show("Username and/or password invalid!");
                    }
                }
            }
            catch (SocketException sEx)
            {
                MessageBox.Show("Socket error! " + sEx.Message);
            }
            catch (IOException ioEx)
            {
                MessageBox.Show("I/O Error! " + ioEx.Message);
            }
            catch (SqlCeException sqlEx)
            {
                MessageBox.Show("SQL error! " + sqlEx.Message);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Login error! " + ex.Message);
            }

            clientSocket.Close();

            if (loginValid)
                return userInfo;
            else
                return null;
        }


        public bool loadActivities(string username, string password, DataSet writeSet)
        {
            bool valid = false;

            try
            {
                clientSocket = new TcpClient(this.serverIp, this.port);
                stream = clientSocket.GetStream();

                // write data
                writeString(stream, "read-residence;" + username + ";" + password);

                // get version vector dataset
                DatabaseConnector dbCon = new DatabaseConnector(username, password);
                writeSet = dbCon.getVersionVectorDataSet("write");
                // write the version vector to the server
                writeDataSet(stream, writeSet);

                // check if can read data
                //string validStr = reader.ReadLine();
                string validStr = readString(stream);

                if (validStr == "valid")
                {
                    // read residence data from server
                    DataSet dsResi = readDataSet(stream);

                    // delete data from database
                    dbCon.deleteAllResidences("active");
                    
                    // manage resi dataset
                    // obtain datareader
                    DataTableReader dtReader = dsResi.CreateDataReader();

                    while (dtReader.Read())
                    {
                        Residence auxRes = new Residence(dtReader);
                        if (dbCon.selectResidence("inactive", auxRes.Resi_id) == null)
                        {
                            dbCon.insertResidence("active", auxRes);
                        }
                    }

                    valid = true;
                }
                else if (validStr == "inconsistent")
                {
                    MessageBox.Show("The server is inconsistent!\nWait some time and try again!");
                }
                else
                {
                    MessageBox.Show("There was an error on the server!");
                }
            }
            catch (SocketException sEx)
            {
                MessageBox.Show("Socket error! " + sEx.Message);
            }
            catch (IOException ioEx)
            {
                MessageBox.Show("I/O Error! " + ioEx.Message);
            }
            catch (SqlCeException sqlEx)
            {
                MessageBox.Show("SQL error! " + sqlEx.Message);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Login error! " + ex.Message);
            }

            clientSocket.Close();

            return valid;
        }

        public bool saveActivities(string username, string password, DataSet listaInativa, DataSet writeSet)
        {
            bool valid = false;

            try
            {
                clientSocket = new TcpClient(this.serverIp, this.port);
                stream = clientSocket.GetStream();

                if (userInfo.AccessLevel == 0)
                {
                    dbCon = new DatabaseConnector(username, password);
                    listaInativa.Clear();
                    dbCon.getResidenceDataSet("inactive", 0, listaInativa);

                    foreach (DataRow dr in listaInativa.Tables["residence"].Rows)
                    {
                        dbCon.insertResidence("active", dbCon.selectResidence("inactive", (int)dr.ItemArray[0]));
                        dbCon.deleteResidence("inactive",(int)dr.ItemArray[0]);
                    }

                    listaInativa.Clear();
                    dbCon.getResidenceDataSet("inactive",listaInativa);
                }

                /* do nothing if there is no residence */
                if (userInfo.AccessLevel > 0 &&
                        listaInativa.Tables["residence"].Rows.Count == 0)
                {
                    MessageBox.Show("You have to fill at least one residence\nto be able to send data to the server!");
                    return false;
                }
                else if (userInfo.AccessLevel == 0 &&
                        listaInativa.Tables["residence"].Rows.Count == 0)
                {
                    MessageBox.Show("You have unaccepted residences in the list! First " +
                     "synchronize with your supervisor before writing anything to the server!");
                    return false;
                }


                // write data
                writeString(stream, "write-residence;" + username + ";" + password);
                
                // write the dataset
                writeDataSet(stream, listaInativa);

                // check to see if the data is valid
                string result = readString(stream);

                if (result == "valid")
                {
                    DataSet newVersion = readDataSet(stream);

                    if (newVersion.Tables.Count == 0)
                    {
                        MessageBox.Show("There are some conflicts\nCheck your data!");
                    }
                    else if (newVersion.Tables["version_vector"].Rows.Count < 
                        listaInativa.Tables["residence"].Rows.Count)
                    {
                        MessageBox.Show("Not all data could be written!");
                      }

                    // merge the write-set dataset
                    writeSet.Merge(newVersion, false);

                    // update the pda database
                    DataTableReader dtReader = writeSet.CreateDataReader();

                    DatabaseConnector dbCon = new DatabaseConnector(username, password);

                    while (dtReader.Read())
                    {
                        dbCon.insertVersionVector("write", new VersionVector(dtReader));
                        dbCon.deleteResidence("inactive",dtReader.GetInt32(0));
                    }                   
                    
                    valid = true;
                }
                else
                {
                    MessageBox.Show("Database error!");
                }
            }
            catch (SocketException sEx)
            {
                MessageBox.Show("Socket error! " + sEx.Message);
            }
            catch (IOException ioEx)
            {
                MessageBox.Show("I/O Error! " + ioEx.Message);
            }
            catch (SqlCeException sqlEx)
            {
                MessageBox.Show("SQL error! " + sqlEx.Message);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Login error! " + ex.Message);
            }

            clientSocket.Close();

            return valid;
        }

    }
}
